//---------------------------------------------------------------------------
// The confidential and proprietary information contained in this file may
// only be used by a person authorised under and to the extent permitted
// by a subsisting licensing agreement from ARM Limited.
//
//            (C) COPYRIGHT 2003-2012 ARM Limited.
//                ALL RIGHTS RESERVED
//
// This entire notice must be reproduced on all copies of this file
// and copies of this file may only be made by a person if such person is
// permitted to do so under the terms of a subsisting license agreement
// from ARM Limited.
//
// Checked In          :  2012-02-07 19:24:33 +0000 (Tue, 07 Feb 2012)
//
// Revision            : 124850
//
// Release Information : PL401-r0p1-00eac0
//
//---------------------------------------------------------------------------
//
// Purpose: AxiFrbmMacro
//
//---------------------------------------------------------------------------

//-------------------------------------------------------------------------
// Include
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
// Definitions
//-------------------------------------------------------------------------


#include "../include/frbm_types.h"
#include <cmath>
#include <sstream>

using namespace frbm_namespace;

ostream& frbm_namespace::operator<< (ostream& os, frbm_check check)
{
  switch (check)
  {
    case eq: os << "eq"; break;
    case ne: os << "ne"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, frbm_ref reference)
{
  switch (reference)
  {
    case addr_id: os << "addr_id"; break;
    case sync:   os << "sync"; break;
    case addr:   os << "addr"; break;
    case wfirst: os << "wfirst"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, frbm_quit quit)
{
  switch (quit)
  {
    case quit_stop:      os << "quit_stop"; break;
    case quit_emit:      os << "quit_emit"; break;
    case quit_idle:      os << "quit_idle"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_align myalign)
{
  switch (myalign)
  {
    case align:   os << "align"; break;
    case unalign: os << "unalign"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_wait mywait)
{
  switch (mywait)
  {
    case ahb_wait:   os << "wait"; break;
    case ahb_nowait: os << "nowait"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_transfer transfer)
{
  switch (transfer)
  {
    case ahb_idle:   os << "ahb_idle"; break;
    case ahb_busy:   os << "ahb_busy"; break;
    case ahb_nonseq: os << "ahb_nonseq"; break;
    case ahb_seq:    os << "ahb_seq"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_burst burst)
{
  switch (burst)
  {
    case axi_fixed: os << "fixed"; break;
    case axi_incr:  os << "incr"; break;
    case axi_wrap:  os << "wrap"; break;
    case incr:  os << "incr"; break;
    case wrap:  os << "wrap"; break;
    case ahb_single:  os << "single"; break;
    case ahb_incr:    os << "incr"; break;
    case ahb_incr4:   os << "incr4"; break;
    case ahb_incr8:   os << "incr8"; break;
    case ahb_incr16:  os << "incr16"; break;
    case ahb_wrap4:   os << "wrap4"; break;
    case ahb_wrap8:   os << "wrap8"; break;
    case ahb_wrap16:  os << "wrap16"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_lock mylock)
{
  switch (mylock)
  {
    case nolock: os   << "nolock"; break;
    case lock:   os   << "lock"; break;
    case axi_excl: os << "excl"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_direction direction)
{
  switch (direction)
  {
    case write: os << "write"; break;
    case read:  os << "read"; break;
    default: os << "ERROR"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_size size)
{
  switch (size)
  {
    case size8:
      os << "size8"; break;
    case size16:
      os << "size16"; break;
    case size32:
      os << "size32"; break;
    case size64:
      os << "size64"; break;
    case size128:
      os << "size128"; break;
    case size256:
      os << "size256"; break;
    case size512:
      os << "size512"; break;
    case size1024:
      os << "size1024"; break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_resp resp)
{
  switch (resp)
  {
    case okay:     os << "okay"; break;
    case axi_exokay:   os << "exokay"; break;
    case axi_slverr:   os << "slverr"; break;
    case axi_decerr:   os << "decerr"; break;
    case ahb_error:    os << "error";  break;
  }
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, amba_length length)
{
  os << (int)length;
  return os;
}

ostream& frbm_namespace::operator<< (ostream& os, transaction trans)
{

  os << "Transaction: " << endl;
  os << "Address : " << hex << trans.address << dec << endl;
  os << "Size : " << trans.size << " (" << int(trans.size) << ")" << endl;
  os << "Length : " << (int)trans.length << endl;
  os << "Burst : " << trans.burst << endl;
  os << "Cache : " << trans.cache << endl;
  os << "Dir : " << trans.direction << endl;

  for (int i = 0; i < trans.length; i++) {

       os << "Beat[" << i << "] = " << trans.data[i] << endl;
       os << "bnumber[" << i << "] = " << trans.bnumber[i] << endl;
       if (trans.direction == rd) {
         if (trans.mask[i] != "") {
            os << "Mask[" << i << "] = " << trans.mask[i] << endl;
         }
            os << "Resp[" << i << "] = " << trans.resp[i] << endl;
       } else {
         if (trans.strobe[i] != "") {
            os << "strobe[" << i << "] = " << trans.strobe[i] << endl;
         }
       };
  };

  if (trans.direction == wr) {
            os << "Resp = " << trans.resp[0] << endl;
  };

  return os;
}


//------------------------------------------------------------------
//reset_timings
//------------------------------------------------------------------
void transaction::reset_timings() {

//clear all timings

    aemit_code = 0;
    await_code = 0;
    aemit_code_s = 0;
    await_code_s = 0;
    remit_code = 0;
    rwait_code = 0;
    remit_code_s = 0;
    rwait_code_s = 0;

    AVWait = 0;
    ARWait = 0;
    RVWait = 0;
    RRWait = 0;
    AVWait_s = 0;
    ARWait_s = 0;
    RVWait_s = 0;
    RRWait_s = 0;

    for (unsigned int i = 0; i < 256; i++)
    {

        DVWait[i] = 0;
        DRWait[i] = 0;
        DVWait_s[i] = 0;
        DRWait_s[i] = 0;

        demit_code[i] = 0;
        dwait_code[i] = 0;

        demit_code_s[i] = 0;
        dwait_code_s[i] = 0;
    }
}

//------------------------------------------------------------------
//reset_data
//------------------------------------------------------------------
void transaction::reset_data() {

    //clear all data and responses
    strobe.resize(256);
    mask.resize(256);
    data.resize(256);
    bnumber.resize(256);
    resp.resize(256);
    user.resize(256);
    auser.resize(256);

    for (unsigned int i = 0; i < 256; i++)
    {
        user[i] = string(USER_MAX_WIDTH,'0');
        resp[i] = okay;
        strobe[i] = "";
        mask[i] = "";
        data[i] = "";
        bnumber[i] = "";
        auser[i] = string(NIC_AUSER_MAX_WIDTH,'0');
    }
}

//------------------------------------------------------------------
// number_bytes
//------------------------------------------------------------------
void transaction::number_bytes() {

     int number = 0;

     //Set the the bnumber strings accordin to the size of the transaction
     for (int len = 0; len < length; len ++) {

             ostringstream data_str;

             //for each byte in the beat
             for (int byte = (int)pow(2.0, (int)size) ; byte > 0; byte--) {

                 data_str << "b" << hex << setw(4) << setfill('0') << number + byte - 1;
             };

             //set in transaction
             bnumber[len] = data_str.str();

             number = number + (int)pow(2.0, (int)size);
     };
}

//------------------------------------------------------------------
// Tidy Transaction
// Ensure that transaction has the correct number of
// data, mask and strobe beats
//------------------------------------------------------------------
void transaction::tidy() {

     int bytes = (int)pow(2.0, (int)size);

     //Set the the bnumber strings accordin to the size of the transaction
     for (int len = 0; len < length; len ++) {

             //Check the data ..
             if (data[len].size() > (bytes * 2)) {
                 data[len] = data[len].substr(data[len].size() - (bytes * 2), bytes * 2);
             } else {
                 data[len].resize(bytes * 2, '0');
             };

             //Check the mask
             if (mask[len].size() > (bytes * 2)) {
                 mask[len] = mask[len].substr(mask[len].size() - (bytes * 2), bytes * 2);
             } else {
                 mask[len].resize(bytes * 2, 'F');
             };

             //Check the strobe
             if (strobe[len].size() > bytes) {
                 strobe[len] = strobe[len].substr(strobe[len].size() - bytes, bytes);
             } else {
                 strobe[len].resize(bytes, '1');
             };
     };

     //Correct stobes to take account of address
     correct_strobes();
}

//------------------------------------------------------------------
//reset
//------------------------------------------------------------------

void transaction::reset(amba_direction read_n_wr) {

          //clear everything
          comment = "";
          location = "";
          final_dest = "external";
          dest_type = "memory";
          dest_sec = "nsec";
          valid = 0;
          region = 0;
          direction = read_n_wr;
          address = 0;
          id = 0;
          locked = nolock;
          unlocking = false;
          cache = 3;
          prot = 0;
          length = 1;
          size = size32;
          burst = axi_incr;
          ref = addr_id;
          check = eq;
          repeat = 1;
          qv = 0;
          vnet = 0;
          ruser = string(BUSER_MAX_WIDTH,'0');
          protocol = axi;

          reset_timings();
          reset_data();

}

//------------------------------------------------------------------
//reset_data
//------------------------------------------------------------------
transaction::transaction(amba_direction read_n_write) {

          reset(read_n_write);
  }

//------------------------------------------------------------------
//address functions
//------------------------------------------------------------------
void transaction::address_rand() {

     address = (unsigned long long) rand();

     //Allign the address
     address_align();

}

void transaction::address_rand(unsigned long long addr_min, unsigned long long addr_max) {

     unsigned long long full_address;

     full_address = ((unsigned long long)rand() << 32) + (unsigned long long)rand();

     if (addr_min == addr_max) {
             address = addr_min;
     } else if (addr_min > addr_max) {
             address = full_address % (addr_min - addr_max) + addr_max;
     } else {
             address = full_address % (addr_max - addr_min) + addr_min;
     }

     //Allign the address
     address_align();

}

void transaction::address_align() {

    int offset;

    //Allign the address if it's a wrap or ahb trans
    if (burst >= 2 or protocol != axi) {
       offset = address % (unsigned long long)pow(2.0, (int)size);
       address = address - offset;
    };

    if (protocol == ahb) {
      //Check if burst will cross 1kb boundary .. if it will modify address
      //so it goes up to the 1k boundary
      offset = address % 0x400;
      if ((offset + (pow(2.0, (int)size) * (int)length)) > 0x400) {
        address = address - offset + (unsigned long long)(0x400 - (pow(2.0, (int)size) * (int)length));
      }
    } else {

      //Check if burst will cross 4kb boundary .. if it will modify address
      //so it goes up to the 4k boundary
      offset = address % 0x1000;
      if ((offset + (pow(2.0, (int)size) * (int)length)) > 0x1000) {
        address = address - offset + (unsigned long long)(0x1000 - (pow(2.0, (int)size) * (int)length));
      }
    }
    //correct_strobes
    correct_strobes();

}

void transaction::correct_strobes() {

    int offset;
    int beats = 1;

    //determine the address offset
    offset = address % ((unsigned long long)pow(2.0, (int)size));

    //determine number of beats to align
    if (burst == axi_fixed) {
            beats = length;
    };

    //unstobe unaligned data
    for(int beat=0; beat < beats; beat++) {
        for(int i=0; i < offset; i++) {
            strobe[beat].replace(strobe[beat].size()-1-i,1,"0");
            mask[beat].replace(mask[beat].size()-2-i-i,2,"00");
        };
    };

}



//------------------------------------------------------------------
// randomise -does not randomise direction
//------------------------------------------------------------------

void transaction::rand_cache() {

        //Randomise cache
        cache = rand() % 16;

        //IF cache[1] is 0 cache[2] and cache[3] must be as well
        if (cache % 4 < 2) {
                cache = rand() % 2;
        }
}

void transaction::randomise(unsigned long long addr_min, unsigned long long addr_max, amba_direction i_direction, amba_size i_size, arm_uint16 i_length, amba_burst i_burst) {

   try {

    ostringstream data_str;

    //check length is valid ... others valid by type
    assert(length >= 1 && length <= 256);

    //Clear transaction
    reset_timings();
    reset_data();

    //Set inputs
    direction = i_direction;
    length = i_length;
    size = i_size;
    burst = i_burst;

    //randomly generate the other values
    rand_cache();

    //prot cannot be
    prot = rand() % 8;

    locked = (amba_lock)(rand() % 3);

    int i;
    for (i = 0; i < length; i++) {

            //data
            for (int j = 0; j < (int)pow(2.0, (int)i_size); j++) {
                 data_str << setw(2) << setfill('0') << hex << (rand() % 256);
            };
            data[i] = data_str.str();
            data_str.str("");

            //Mask
            for (int j = 0; j < (int)pow(2.0, (int)i_size); j++) {
                 data_str << setw(2) << setfill('0') << hex << 255;
            };
            mask[i] = data_str.str();
            data_str.str("");

            //strobes
            for (int j = 0; j < (int)pow(2.0, (int)i_size); j++) {
               //no strobes on AHB
               if (burst < 3) data_str << rand() % 2;
               else data_str << "1";
            }
            strobe[i] = data_str.str();
            data_str.str("");

            //response
            if (burst < 3) {
                 //AXI
                 resp[i] = (amba_resp)(rand() % 4);
            } else {
                 resp[i] = (amba_resp)(rand() % (ahb_error + 1));
                 if (resp[i] > okay && resp[i] < ahb_error)
                      resp[i] = okay;
            }
    }

    //randomise the address
    address_rand(addr_min, addr_max);


   } catch ( const std::exception& ex ) {
           cout << "Caught : " <<  ex.what() << endl;
   }

}
